#include "scenegraph.h"
#include "node.h"
#include <d3dx9.h>
#include "../../core/resources/resourcemanager.h"
#include "../../core/resources/resourcerendertarget.h"
#include "../../core/resources/resourceeffect.h"
#include "../../core/resources/resourceactivetechnique.h"
#include "../../core/resources/resourceeffectvariables.h"

namespace gunsmith
{
CSceneGraph::CSceneGraph(CNode* pNodeRoot)
{
	m_pResMngr		= CResourceManager::GetInstance();
	m_pDevice		= m_pResMngr->GetDevice();
	m_bDebugMode	= false;
	m_bNeedToUpdate = false;

	// get and create important resources
	m_pRenderTargetDisplay	= m_pResMngr->GetResourcesByName<CResourceRenderTarget>(RID_RENDERTARGET_DEVICE);
	m_pResActiveTechnique	= m_pResMngr->CreateResource<CResourceActiveTechnique>(RID_ACTIVE_TECHNIQUE);
	m_pResEVars				= m_pResMngr->CreateResource<CResourceEffectVariables>(RID_EFFECT_VARIABLES);

	CreateFonts();
	
	CreateScreenQuad();
	SetRoot(pNodeRoot);
}

CSceneGraph::~CSceneGraph()
{
	m_pResMngr->Release();
	m_pVertexBuffer->Release();
	m_pVertexDecl->Release();
	m_pFont->Release();
	m_pSprite->Release();
}

/*!	\brief		Updates the scene graph. (see remarks)
	\param		fDelta Delta time.
	\return		void
	\remarks	The scene graph updates all nodes attached to the root node. If something in the graph changes a node will notify the graph
				that it needs to update its hash map of the nodes attached.
	\sa			CNode::Remove()*/
void CSceneGraph::Update(float fDelta)
{
	m_fLastDelta = fDelta;

	if(m_bNeedToUpdate)
		PopulateHashMap();

	UpdateTraverse(m_pNodeRoot, fDelta);
}

/*!	\brief		Sets the root node. (see remarks)
	\param		pNode
	\return		void
	\remarks	Invokes PopulateHashMap().
	\sa			*/
void CSceneGraph::SetRoot(CNode *pNode)
{
	m_pNodeRoot = pNode;
	m_pNodeRoot->SetSceneGraph(this);
	PopulateHashMap();
}

/*!	\brief		Traverses all nodes and invokes their Update methods.
	\param		pNodeBase	Base node.
	\param		fDelta		Delta time.
	\return		void
	\remarks	
	\sa			*/
void CSceneGraph::UpdateTraverse(CNode *pNodeBase, float fDelta)
{
	if(pNodeBase)
		pNodeBase->Update(fDelta);
	else
		return;

	if(pNodeBase->GetNextSibbling())
		UpdateTraverse(pNodeBase->GetNextSibbling(), fDelta);

	if(pNodeBase->GetFirstChild())
		UpdateTraverse(pNodeBase->GetFirstChild(), fDelta);
}

/*!	\brief		Traverses all nodes and invokes their Draw methods.
	\param		pNodeBase Base node.
	\return		void
	\remarks	
	\sa			*/
void CSceneGraph::DrawTraverse(CNode *pNodeBase)
{
	if(!pNodeBase)
		return;

	pNodeBase->Draw();

	if(pNodeBase->GetNextSibbling())
		DrawTraverse(pNodeBase->GetNextSibbling());

	if(pNodeBase->GetFirstChild())
		DrawTraverse(pNodeBase->GetFirstChild());
}

/*!	\brief		Draws the actual scene. (see remarks)
	\return		void
	\remarks	Invokes DrawTraverse().
	\sa			*/
void CSceneGraph::Draw()
{
	m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x1b1d21, 1, 0);
	m_pDevice->BeginScene();

	OutputFramesPerSec(m_fLastDelta);

	DrawTraverse(m_pNodeRoot);

	m_pDevice->EndScene();
	m_pDevice->Present(NULL, NULL, NULL, NULL);
}

/*!	\brief		Traverses all nodes beginning with route and adds them to the hashmap. (see remarks)
	\return		void
	\remarks	Clears the hashmap, invokes AddNodesToHashMap(m_pNodeRoot) and sets m_bNeedToUpdate to false.
	\sa			*/
void CSceneGraph::PopulateHashMap()
{
	m_mSceneObjects.clear();
	AddNodesToHashMap(m_pNodeRoot);
	m_bNeedToUpdate = false;
}

/*!	\brief		Adds nodes to the hashmap.
	\param		pNodeBase	Base node.
	\return		void
	\remarks	
	\sa			*/
void CSceneGraph::AddNodesToHashMap(CNode *pNodeBase)
{
	if(!pNodeBase)
		return;

	if(pNodeBase->GetNextSibbling())
		AddNodesToHashMap(pNodeBase->GetNextSibbling());
	if(pNodeBase->GetFirstChild())
		AddNodesToHashMap(pNodeBase->GetFirstChild());

	pNodeBase->m_pSceneGraph = this;
	m_mSceneObjects.insert(pair<ENODETYPE, CNode*>(pNodeBase->GetNodeType(), pNodeBase));
}

/*!	\brief		Deletes all nodes in the scene aswell as the scene graph itself.
	\return		void
	\remarks
	\sa			*/
void CSceneGraph::DestroyTree()
{
	PopulateHashMap();

	NODETYPEMAP::iterator itMap = m_mSceneObjects.begin();

	while(itMap != m_mSceneObjects.end())
	{
		delete (*itMap).second;					// delete CNode*
		itMap = m_mSceneObjects.erase(itMap);	// remove from map
	}
}

/*!	\brief		Sets the debug mode to true or false. (see remarks)
	\param		bDebugMode Debug mode.
	\return		void
	\remarks	DrawDebug()
	\sa			*/
void CSceneGraph::SetDebug(bool bDebugMode)
{
	m_bDebugMode = bDebugMode;
}

void CSceneGraph::DrawTo(std::string sTechnique, CResourceRenderTarget *pResRenderTarget)
{
	m_pResActiveTechnique->SetActive(sTechnique);
	m_mEffects.insert(make_pair(sTechnique, pResRenderTarget));	
	
	m_pDevice->SetRenderTarget(0, pResRenderTarget->GetSurface());
	m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x1b1d21, 1, 0);
	m_pDevice->BeginScene();

	DrawTraverse(m_pNodeRoot);

	m_pDevice->EndScene();
}

void CSceneGraph::DrawCombined(CResourceEffect *pResEffect)
{
	m_pDevice->SetRenderTarget(0, m_pRenderTargetDisplay->GetSurface());
	m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x1b1d21, 1, 0);
	m_pDevice->BeginScene();

	// (1st) set all render-targets as textures
	for(EFFECTMAP::iterator it = m_mEffects.begin(); it != m_mEffects.end(); it++)
	{
		// ... and set them as "gTex" + "technique name"
		std::string sTextureName = "gTex" + (*it).first;
		pResEffect->GetEffect()->SetTexture(sTextureName.c_str(), (*it).second->GetTexture());
	}
	
	// (2nd) use the given resource effect to combine the buffers
	UINT iPasses;
	pResEffect->GetEffect()->Begin(&iPasses, D3DXFX_DONOTSAVESTATE);

	for(UINT i = 0; i < iPasses; ++i)
	{
		pResEffect->GetEffect()->BeginPass(i);
		DrawScreenQuad();						// draws a quad to the screen where we output the combined textures
		pResEffect->GetEffect()->EndPass();
	}

	pResEffect->GetEffect()->End();

	// (3rd) draw frames per second to the screen
	OutputFramesPerSec(m_fLastDelta);

	// (4th) present to screen
	m_pDevice->EndScene();
	m_pDevice->Present(NULL, NULL, NULL, NULL);

	m_mEffects.clear();
}

void CSceneGraph::CreateScreenQuad()
{
	struct TVertex
	{
		D3DXVECTOR3 pos;
		float		u, v;
	};

	D3DVERTEXELEMENT9 pVertexElements[] = {
		0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0,
		0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0,
		D3DDECL_END()
	};

	// create declaration and buffer
	m_pDevice->CreateVertexDeclaration(pVertexElements, &m_pVertexDecl);
	m_pDevice->CreateVertexBuffer(6*sizeof(TVertex), 0, 0, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL);

	// set vertex positions
	TVertex vertices[6];
	vertices[0].pos = D3DXVECTOR3(-1, -1, 0); vertices[0].u = 0; vertices[0].v = 1;
	vertices[1].pos = D3DXVECTOR3(-1, +1, 0); vertices[1].u = 0; vertices[1].v = 0;
	vertices[2].pos = D3DXVECTOR3(+1, -1, 0); vertices[2].u = 1; vertices[2].v = 1;
	vertices[3] = vertices[1];
	vertices[4].pos = D3DXVECTOR3(+1, +1, 0); vertices[4].u = 1; vertices[4].v = 0;
	vertices[5] = vertices[2];
	
	// copy data
	void *pData;
	m_pVertexBuffer->Lock(0, 0, &pData, 0);
	memcpy(pData, vertices, 6*sizeof(TVertex));
	m_pVertexBuffer->Unlock();
}

void CSceneGraph::DrawScreenQuad()
{
	m_pDevice->SetVertexDeclaration(m_pVertexDecl);
	m_pDevice->SetStreamSource(0, m_pVertexBuffer, 0, 20); 
	m_pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
}

CNode* CSceneGraph::GetRoot() const
{
	return m_pNodeRoot;
}

/*!	\brief		Creates the fonts needed to draw text. (see remarks)
	\return		void
	\remarks	Also creates a sprite to accelerate font drawing.
	\sa			*/
void CSceneGraph::CreateFonts()
{
	D3DXCreateFont(m_pDevice, 15, 0, FW_LIGHT, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
					DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Arial", &m_pFont);
	D3DXCreateSprite(m_pDevice, &m_pSprite);
}

/*!	\brief		Draws the provided text on screen.
	\param		cpText	Text to draw.
	\return		void
	\remarks	
	\sa			*/
void CSceneGraph::DrawOnScreenText(const char* cpText)
{
	m_pSprite->Begin(D3DXSPRITE_ALPHABLEND);
		m_pFont->DrawText(m_pSprite, cpText, -1, NULL, DT_TOP | DT_LEFT, 0xffffffff);
	m_pSprite->End();
}

/*!	\brief		Draws frames per second on the screen.
	\param		fDelta	Delta time.
	\return		void
	\remarks	
	\sa			*/
void CSceneGraph::OutputFramesPerSec(float fDelta)
{
	static UINT iFrameCounter = 0;
	static float fCounter = 0.0f;
	static UINT iFrames = 0;

	++iFrameCounter;
	fCounter += fDelta;

	if(fCounter >= 1.0f)
	{
		iFrames = iFrameCounter;
		fCounter -= 1.0f;
		iFrameCounter = 0;
	}

	char pOutputText[128];
	sprintf_s(pOutputText, 128, " Frames Per Second: %d", iFrames);
	DrawOnScreenText(pOutputText);
}
};